#include "BoidBase.h"
#include "BoidManager.h"
#include "BehaviorSeek.h"
#include "BehaviorSeparation.h"
#include "BehaviorCohesion.h"
#include "BehaviorAlignment.h"

//! constructor.
BoidBase::BoidBase(OgreOde::Body *body) :
	mBody(body), mMinSightRadius(0.0f), mMaxSightRadius(0.0f),
	mCosSightAngle(0.0f), mNeedRefreshNeighbours(false)
{
	BoidManager::getSingletonPtr()->mBoidInstanceList.push_back(this);
}

//! destructor.
BoidBase::~BoidBase()
{
	BoidManager::getSingletonPtr()->mBoidInstanceList.remove(this);
	clearBehaviors();
}

//! check if the given boid is in the neighborhood.
bool BoidBase::isInNeighborhood(const BoidBase *other, float minDistance, float maxDistance, float cosMaxAngle) const
{
	float squaredDistance = getPosition().squaredDistance(other->getPosition());
	if (squaredDistance < minDistance * minDistance)
	{
		return true;
	}
	else if (squaredDistance < maxDistance * maxDistance)
	{
		// test angular offset from forward axis
		Ogre::Vector3 unitOffset(other->getPosition() - getPosition());
		unitOffset.normalise();
		float forwardness = getForwardVector().dotProduct(unitOffset);
		if (forwardness > cosMaxAngle)
		{
			return true;
		}
	}

	return false;
}

//! returns the neighbors of this boid.
const BoidList& BoidBase::getNeighbors() const
{
	if (mNeedRefreshNeighbours)
	{
		BoidManager::getSingletonPtr()->getNeighborhoodBoids(
			const_cast<BoidBase *>(this), mMinSightRadius, mMaxSightRadius, mCosSightAngle,
			const_cast<BoidList &>(mBoidNeighbours));

		const_cast<bool &>(mNeedRefreshNeighbours) = false;
	}

	return mBoidNeighbours;
}

//! add behavior.
Behavior *BoidBase::addBehavior(BehaviorType type)
{
	Behavior *behavior;
	switch (type)
	{
		case BEHAVIOR_SEEK:
			behavior = new BehaviorSeek();
			break;

		case BEHAVIOR_SEPARATION:
			behavior = new BehaviorSeparation();
			break;

		case BEHAVIOR_COHESION:
			behavior = new BehaviorCohesion();
			break;

		case BEHAVIOR_ALIGNMENT:
			behavior = new BehaviorAlignment();
			break;

		default:
			return 0;
	};

	mBehaviorList.push_back(behavior);
	return behavior;
}

//! remove specific behavior.
void BoidBase::removeBehavior(Behavior *behavior)
{
	mBehaviorList.remove(behavior);
	delete behavior;
}

//! get behaviors.
const BoidBase::BehaviorList &BoidBase::getBehaviors()
{
	return mBehaviorList;
}

//! returns the number of behaviors.
int BoidBase::getBehaviorCount()
{
	return mBehaviorList.size();
}

//! clears behavior list.
void BoidBase::clearBehaviors()
{
	BehaviorList::iterator iter = mBehaviorList.begin();
	while (iter != mBehaviorList.end())
	{
		delete *iter;
		iter++;
	}
	mBehaviorList.clear();
}

//! returns the combined weighted desired steering vector.
Ogre::Vector3 BoidBase::getBehaviorResult()
{
	mNeedRefreshNeighbours = true;

	Ogre::Vector3 desiredSteering(0.0f, 0.0f, 0.0f);
	BehaviorList::iterator iter = mBehaviorList.begin();
	while (iter != mBehaviorList.end())
	{
		float weight = (*iter)->mWeight;
		if (weight > 0)
		{
			desiredSteering += ((*iter)->execute(this) * weight);
		}

		iter++;
	}

	return desiredSteering;
}
